// Copyright 2010 Abhishek Kulkarni. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package goprop

import (
	"time"
	"flag"
	"runtime"
	"strconv"
	"reflect"
)

/*
 Propagation networks propose a shift in the foundation of computation,
 by moving away from rigid, time-bound expression-evaluation computation
 paradigm to a more general-purpose propagation-oriented paradigm where
 information flow is multidirectional.

 Programs are written by architecting them as propagation networks, as
 computational modules that are arranged in parallel or in series, much
 like elements in an electric circuit.
*/

// Possible states (for cells and propagators)
const (
	Stopped = iota
	Running
	Idle
)

type States int

// Possible commands
const (
	Stop = iota
	AddContent
	Content
	Run
)

type Msg struct {
	Cmd	    uint8
	Value 	    interface{}
}

type GenericFn func(reflect.Value) interface{}

func idle() { time.Sleep(1e6) }

type PropagatorNetwork struct {
	Name        string
	Index       uint
	Quiet       chan bool
	Propagators []*Propagator
}

// create a new propagator network
func NewPropagatorNetwork(name string) *PropagatorNetwork {
	flag.Parse()
	setDebugLevel(*debugFlags)
	pn := &PropagatorNetwork{
		Name:        name,
		Index:       0,
		Quiet:       make(chan bool),
	}

	Debug(dbgPropagator, "== starting propagator network %s\n", pn.Name)
	return pn
}

// add a new cell to a given propagator network
func (pn *PropagatorNetwork) NewCell(name string) *Cell {
	c := &Cell{
		Name:      name,
		Network:   pn,
		SChan:     make(chan Msg, 5),
		State:     Idle,
		Neighbors: make(map[string]*Propagator),
	}

	Debug(dbgCell, "** created cell %s in %s\n", c.Name, pn.Name)
	go c.Listen()
	return c
}

// add a new propagator to a given propagator network
func (pn *PropagatorNetwork) NewPropagator(name string, f GenericFn, ics, ocs interface{}) {
	p := &Propagator{
		Name:     name + "-" + strconv.Uitoa(pn.Index),
		Network:  pn,
		SChan:    make(chan Msg, 5),
		State:    Idle,
		Function: f,
	}

	if ics != nil {
		p.Inputs = ics.([]*Cell)
		for i := range p.Inputs {
			p.Inputs[i].NewNeighbor(p)
		}
	}

	if ocs != nil {
		p.Outputs = ocs.([]*Cell)
	}

	Debug(dbgPropagator, ">> created propagator %s in %s\n", p.Name, pn.Name)
	pn.Index++
	pn.Propagators = append(pn.Propagators, p)
	p.Run()
	go p.Listen()
}

// check if the propagator network is in a quiescent state or not
func (pn *PropagatorNetwork) Quiescent() chan bool {
	go func() {
	again:
		idle()
		for _, p := range pn.Propagators {
			if p.State == Running {
				// Wait for the network to be quiet
				idle()
				runtime.Gosched()
				goto again
			}
		}
		Debug(dbgPropagator, "== network %s is in a quiescent state\n", pn.Name)
		pn.Quiet <- true
	}()
	return pn.Quiet
}

// stop a propagator network
func (pn *PropagatorNetwork) Stop() {
	// Stop all propagators
	for _, p := range pn.Propagators {
		for _, c := range p.Outputs {
			c.Destroy()
		}
		p.Destroy()
	}

	Debug(dbgPropagator, "== stopping propagator network %s\n", pn.Name)
}
